using System;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Collections.Generic;
using Wintellect.PowerCollections;
using System.Reflection;

namespace DbSharp.Framework.Diff
{

    /// <summary>
    /// difference statistics result for 2 strings
    /// </summary>
    public class TextDiffStat
    {
        public long Rev1Lines { get; set; }
        public long Rev2Lines { get; set; }
        public long AddedLines { get; set; }
        public long UnchangedLines { get; set; }
        public long DeletedLines { get; set; }
        public bool LogDiffResult { get; set; }
        public StringBuilder DiffResult = new StringBuilder();
    }

    [Obfuscation]
    public enum LegendDisplayFlag
    {
        DisplayCommon,
        DisplayAdded,
        DisplayDeleted
    }

    /// <summary>
    /// Provides methods for diffing text and items.
    /// </summary>
    public static class DiffHtmlOutputHelper
    {
        /// <summary>
        /// Computes the difference between two revisions.
        /// </summary>
        /// <param name="rev1">The first revision.</param>
        /// <param name="rev2">The second revision.</param>
        /// <param name="logDiffResult">whether to log diff into StringBuilder</param>
        /// <returns>The XHTML-formatted result.</returns>
        public static TextDiffStat DetectRevisionsDiff(string rev1, string rev2, bool logDiffResult)
        {
            if (rev1 == null)
                rev1 = string.Empty;
            if (rev2 == null)
                rev2 = string.Empty;
            string[] aLines = rev1.Split('\n'); ;
            string[] bLines = rev2.Split('\n');

            TextDiffStat mark = new TextDiffStat();
            mark.LogDiffResult = logDiffResult;
            mark.Rev1Lines = aLines.Length;
            mark.Rev2Lines = bLines.Length;

            Difference.Item[] f = Difference.DiffText(rev1, rev2, false, false, false);

            StringBuilder result = mark.DiffResult;
            if (mark.LogDiffResult)
                result.Append(@"<table cellpadding=""0"" cellspacing=""0"" style=""color: #000000; background-color: #FFFFFF;font-family:Consolas, monospace;"">");

            int lineNumber = 0;
            int deletedLineNumber = 0;
            for (int fdx = 0; fdx < f.Length; fdx++)
            {
                Difference.Item aItem = f[fdx];
                // Write unchanged lines
                while ((lineNumber < aItem.StartB) && (lineNumber < bLines.Length))
                {
                    if (mark.LogDiffResult)
                        result.Append(WriteLine(lineNumber, "", bLines[lineNumber]));
                    lineNumber++;
                    mark.UnchangedLines++;
                }

                // Write deleted lines
                for (int m = 0; m < aItem.deletedA; m++)
                {
                    deletedLineNumber++;
                    if (mark.LogDiffResult)
                        result.Append(WriteLine(-1 * deletedLineNumber, "d", aLines[aItem.StartA + m]));
                    mark.DeletedLines++;
                }

                // Write inserted lines
                while (lineNumber < aItem.StartB + aItem.insertedB)
                {
                    if (mark.LogDiffResult)
                        result.Append(WriteLine(lineNumber, "i", bLines[lineNumber]));
                    lineNumber++;
                    mark.AddedLines++;
                }
            }

            // Write the rest of unchanged lines
            while (lineNumber < bLines.Length)
            {
                if (mark.LogDiffResult)
                    result.Append(WriteLine(lineNumber, "", bLines[lineNumber]));
                mark.UnchangedLines++;
                lineNumber++;
            }

            if (mark.LogDiffResult)
                result.Append("</table>");

            return mark;
        }



        /// <summary>
        /// Display the difference between two revisions.
        /// </summary>
        /// <param name="rev1">The first revision.</param>
        /// <param name="rev2">The second revision.</param>
        /// <returns>The XHTML-formatted result with Html tag and Body tag.</returns>
        public static string ComposeHtml_RevisionsDiff(string htmlTitle, string rev1, string rev2)
        {
            string htmlBegin;
            string htmlEnd;
            ComposeHtml_BeginEnd(htmlTitle, out htmlBegin, out htmlEnd);

            StringBuilder html = new StringBuilder();
            html.Append(htmlBegin);
            bool logDiffResult = true;
            HashSet<LegendDisplayFlag> setLegendDisplayFlag = new HashSet<LegendDisplayFlag>();
            setLegendDisplayFlag.Add(LegendDisplayFlag.DisplayAdded);
            setLegendDisplayFlag.Add(LegendDisplayFlag.DisplayCommon);
            setLegendDisplayFlag.Add(LegendDisplayFlag.DisplayDeleted);

            TextDiffStat stat = DetectRevisionsDiff(rev1, rev2, logDiffResult);
            string legend = WriteDiffLegend(setLegendDisplayFlag, stat.AddedLines, stat.DeletedLines, stat.UnchangedLines);

            html.Append(legend);
            html.Append(stat.DiffResult);
            html.Append(legend);

            html.Append(htmlEnd);
            return html.ToString();
        }

        public static string ComposeHtml_ChangedLines(string htmlTitle, HashSet<LegendDisplayFlag> setLegendDisplayFlag, IList<string> addedLines, IList<string> deletedLines)
        {
            string htmlBegin;
            string htmlEnd;
            ComposeHtml_BeginEnd(htmlTitle, out htmlBegin, out htmlEnd);

            StringBuilder html = new StringBuilder();
            html.Insert(0, htmlBegin);

            long addedCount = 0;
            if (addedLines != null)
                addedCount = addedLines.Count;
            long deletedCount = 0;
            if (deletedLines != null)
                deletedCount = deletedLines.Count;

            string legend = WriteDiffLegend(setLegendDisplayFlag, addedCount, deletedCount, 0);

            html.Append(legend);
            html.Append(GetChangedLinesDiffText(addedLines, deletedLines));
            html.Append(legend);

            html.Append(htmlEnd);
            return html.ToString();
        }

        public static string ComposeHtml_CommonLines(string htmlTitle, HashSet<LegendDisplayFlag> setLegendDisplayFlag, IList<string> commonLines)
        {
            string htmlBegin;
            string htmlEnd;
            ComposeHtml_BeginEnd(htmlTitle, out htmlBegin, out htmlEnd);

            StringBuilder html = new StringBuilder();
            html.Insert(0, htmlBegin);

            string legend = string.Empty;
            if (commonLines != null)
                legend = WriteDiffLegend(setLegendDisplayFlag, 0, 0, commonLines.Count);

            html.Append(legend);
            html.Append(GetUnchangedLinesDiffText(commonLines));
            html.Append(legend);

            html.Append(htmlEnd);
            return html.ToString();
        }

        public static string ComposeHtml_UnionLines(string htmlTitle, HashSet<LegendDisplayFlag> setLegendDisplayFlag, IList<string> addedLines, IList<string> deletedLines, IList<string> commonLines)
        {
            string htmlBegin;
            string htmlEnd;
            ComposeHtml_BeginEnd(htmlTitle, out htmlBegin, out htmlEnd);

            StringBuilder html = new StringBuilder();
            html.Insert(0, htmlBegin);

            long addedCount = 0;
            if (addedLines != null)
                addedCount = addedLines.Count;
            long deletedCount = 0;
            if (deletedLines != null)
                deletedCount = deletedLines.Count;
            long commonCount = 0;
            if (commonLines != null)
                commonCount = commonLines.Count;

            string legend = WriteDiffLegend(setLegendDisplayFlag, addedCount, deletedCount, commonCount);

            html.Append(legend);
            html.Append(GetChangedLinesDiffText(addedLines, deletedLines));
            html.Append(GetUnchangedLinesDiffText(commonLines));
            html.Append(legend);

            html.Append(htmlEnd);
            return html.ToString();
        }






        #region Private methods



        private static string WriteDiffLegend(HashSet<LegendDisplayFlag> setLegendDisplayFlag, long addedLines, long deletedLines, long commonLines)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(@"<table  border=""1"" cellpadding=""1"" cellspacing=""0"" style=""color: #000000; background-color: #FFFFFF;"">");
            sb.Append("<tr>");

            //column 1 --legend
            sb.Append(@"<td valign=""top"" style=""font-family:Consolas, monospace; font-weight: bold;font-size: medium"">");
            sb.Append("&nbsp;");
            sb.Append("Legend and summary");
            sb.Append("&nbsp;");
            sb.Append("</td>");

            //column 2 --added
            if (setLegendDisplayFlag.Contains(LegendDisplayFlag.DisplayAdded))
            {
                sb.Append(@"<td valign=""top"" style=""font-family:Consolas, monospace; font-weight: bold;font-size: medium"">");
                sb.Append(@"<div style=""");
                sb.Append("white-space:nowrap;background-color: #88CC33;");
                sb.Append(@""">" + HttpUtility.HtmlEncode("Surplus Lines:" + addedLines) + "</div>");
                sb.Append("</td>");
            }

            //column 3 --deleted
            if (setLegendDisplayFlag.Contains(LegendDisplayFlag.DisplayDeleted))
            {
                sb.Append(@"<td valign=""top"" style=""font-family:Consolas, monospace; font-weight: bold;font-size: medium"">");
                sb.Append(@"<div style=""");
                sb.Append("white-space:nowrap;background-color: #FFDF66;");
                sb.Append(@""">" + HttpUtility.HtmlEncode("Absence Lines:" + deletedLines.ToString()) + "</div>");
                sb.Append("</td>");
            }

            //column 3 --common
            if (setLegendDisplayFlag.Contains(LegendDisplayFlag.DisplayCommon))
            {
                sb.Append(@"<td valign=""top"" style=""font-family:Consolas, monospace; font-weight: bold;font-size: medium"">");
                sb.Append(@"<div style=""");
                sb.Append("white-space:nowrap;");
                sb.Append(@""">" + HttpUtility.HtmlEncode("Common Lines:" + commonLines.ToString()) + "</div>");
                sb.Append("</td>");
            }


            sb.Append("</tr>");
            sb.Append("</table>");
            return sb.ToString();
        }



        private static string WriteLine(int lineNumber, string typ, string line)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(@"<tr valign=""top"">");

            //sb.Append(@"<td valign=""top"" width=""30"" style=""font-family:Consolas, monospace;"">");
            //sb.Append(@"<td valign=""top"" style=""font-family:Consolas, monospace;background-color: #FFDF66;"">");
            //sb.Append(@"<td  style=""font-family:Consolas, monospace;");
            sb.Append(@"<td>");
            if (lineNumber >= 0)
            {
                sb.Append(((int)(lineNumber + 1)).ToString());
            }
            sb.Append("</td>");

            sb.Append(@"<td>");
            sb.Append(@"<div style=""");
            switch (typ)
            {
                //make sure not to wrap line 
                //make sure show distinct color for the added lines and deleted lines
                case "i":
                    sb.Append("white-space:nowrap;background-color: #88CC33;");
                    break;
                case "d":
                    sb.Append("white-space:nowrap;background-color: #FFDF66;");
                    break;
                default:
                    sb.Append("white-space:nowrap;");
                    break;
            }

            sb.Append(@""">" + HttpUtility.HtmlEncode(line) + "</div>");
            sb.Append("</td>");

            sb.Append("</tr>");
            return sb.ToString();
        }


        private static string GetChangedLinesDiffText(IList<string> addedLines, IList<string> deletedLines)
        {
            StringBuilder result = new StringBuilder();
            result.Append(@"<table cellpadding=""0"" cellspacing=""0"" style=""color: #000000; background-color: #FFFFFF;"">");
            // Write inserted lines
            int lineNumber = 0;
            if (addedLines != null)
            {
                while (lineNumber < addedLines.Count)
                {
                    result.Append(WriteLine(lineNumber, "i", addedLines[lineNumber]));
                    lineNumber++;
                }
            }

            // Write deleted lines 
            lineNumber = 0;
            if (deletedLines != null)
            {
                while (lineNumber < deletedLines.Count)
                {
                    result.Append(WriteLine(lineNumber, "d", deletedLines[lineNumber]));
                    lineNumber++;
                }
            }

            result.Append("</table>");

            return result.ToString();
        }


        private static string GetUnchangedLinesDiffText(IList<string> unchangedLines)
        {
            StringBuilder result = new StringBuilder();
            result.Append(@"<table cellpadding=""0"" cellspacing=""0"" style=""color: #000000; background-color: #FFFFFF;"">");

            // Write unchanged lines
            int lineNumber = 0;
            if (unchangedLines != null)
            {
                while (lineNumber < unchangedLines.Count)
                {
                    result.Append(WriteLine(lineNumber, "", unchangedLines[lineNumber]));
                    lineNumber++;
                }
            }

            result.Append("</table>");
            return result.ToString();
        }


        private static void ComposeHtml_BeginEnd(string htmlTitle, out string htmlBegin, out string htmlEnd)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<html>");
            sb.Append("<head>");
            sb.Append(@"<meta http-equiv=""Content-Type"" content=""text/html; charset=utf-8"" />");

            sb.Append("<title>");
            if (!string.IsNullOrEmpty(htmlTitle))
                sb.Append(HttpUtility.HtmlEncode(htmlTitle));
            sb.Append("</title>");
            sb.Append("</head>");
            sb.Append("<body>");

            htmlBegin = sb.ToString();
            sb.Length = 0;
            sb.Append("</body>");
            sb.Append("</html>");
            htmlEnd = sb.ToString();
        }


        private static string ComposeHtml(string htmlTitle, params string[] listBody)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<html>");
            sb.Append("<head>");
            sb.Append(@"<meta http-equiv=""Content-Type"" content=""text/html; charset=utf-8"" />");

            sb.Append("<title>");
            if (!string.IsNullOrEmpty(htmlTitle))
                sb.Append(HttpUtility.HtmlEncode(htmlTitle));
            sb.Append("</title>");
            sb.Append("</head>");
            sb.Append("<body>");
            foreach (string body in listBody)
            {
                sb.Append(body);
            }
            sb.Append("</body>");
            sb.Append("</html>");
            return sb.ToString();
        }


        #endregion


    }



}
